package utils

import (
	"fmt"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/os/gstructs"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/xuri/excelize/v2"
	"strconv"
)

const (
	Tag          = "excel"
	A            = "a" //excel 标题
	P            = "p" //excel 值转换
	MaxCharCount = 26
)

type excelTag struct {
	Alias   string                      `json:"a"`
	Pattern map[interface{}]interface{} `json:"p"`
}

// Export 导出Excel文件
// sheetName 工作表名称
// param 导出参数
func Export[T any](sheetName string, param []T) (*excelize.File, error) {
	if len(param) < 1 {
		return nil, nil
	}
	r, err := gstructs.Fields(gstructs.FieldsInput{
		Pointer:         new(T),
		RecursiveOption: 1,
	})
	if err != nil {
		return nil, nil
	}
	headers := make([]string, 0)
	rows := make([][]interface{}, 0)

	f := excelize.NewFile()
	f.SetSheetName("Sheet1", sheetName)
	sheetIndex := f.GetSheetIndex(sheetName)
	maxColumnRowNameLen := 1 + len(strconv.Itoa(len(rows)))
	columnCount := len(headers)
	if columnCount > MaxCharCount {
		maxColumnRowNameLen++
	} else if columnCount > MaxCharCount*MaxCharCount {
		maxColumnRowNameLen += 2
	}
	columnNames := make([][]byte, 0, columnCount)

	//获取表头
	for _, field := range r {
		excelTags := field.Tag(Tag)
		json := gjson.New(gstr.Replace(excelTags, "'", "\""))
		tagVal := new(excelTag)
		err = json.Scan(tagVal)
		if err != nil {
			return nil, err
		}
		// 设置表头
		headers = append(headers, tagVal.Alias)
	}

	//获取内容，转换数据
	for _, item := range param {
		row := make([]interface{}, 0)
		fields, err := gstructs.Fields(gstructs.FieldsInput{
			Pointer:         item,
			RecursiveOption: 1,
		})
		if err != nil {
			return nil, nil
		}
		for _, field := range fields {
			excelTags := field.Tag(Tag)
			json := gjson.New(gstr.Replace(excelTags, "'", "\""))
			tagVal := new(excelTag)
			err = json.Scan(tagVal)
			if err != nil {
				return nil, err
			}
			val := field.Value.Interface()
			if tagVal.Pattern != nil {
				val = tagVal.Pattern[gconv.String(val)]
			}
			row = append(row, val)
		}
		rows = append(rows, row)
	}

	for i, header := range headers {
		columnName := getColumnName(i, maxColumnRowNameLen)
		columnNames = append(columnNames, columnName)
		// 初始化excel表头，这里的index从1开始要注意
		curColumnName := getColumnRowName(columnName, 1)
		err := f.SetCellValue(sheetName, curColumnName, header)
		if err != nil {
			return nil, err
		}
	}
	for rowIndex, row := range rows {
		for columnIndex, columnName := range columnNames {
			// 从第二行开始
			err := f.SetCellValue(sheetName, getColumnRowName(columnName, rowIndex+2), row[columnIndex])
			if err != nil {
				return nil, err
			}
		}
	}
	f.SetActiveSheet(sheetIndex)
	return f, nil
}

// Import 导入Excel文件
// fileName excel文件
// sheetName 工作表名称
func Import[T any](fileName string, sheetName string) (params []*T, err error) {
	f, err := excelize.OpenFile(fileName)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer func() {
		if err := f.Close(); err != nil {
			fmt.Println(err)
		}
	}()

	// 获取 sheetName 上所有单元格
	rows, err := f.GetRows(sheetName)
	if err != nil {
		return
	}
	params = []*T{}
	r, err := gstructs.Fields(gstructs.FieldsInput{
		Pointer:         new(T),
		RecursiveOption: 1,
	})
	if err != nil {
		return
	}

	//通过标题转换对应字段
	cellMap := make(map[int]excelTag, 0)
	for _, field := range r {
		excelTags := field.Tag(Tag)
		json := gjson.New(gstr.Replace(excelTags, "'", "\""))
		tagVal := new(excelTag)
		err = json.Scan(tagVal)
		if err != nil {
			return
		}
		//第一行为标题
		for i, colCell := range rows[0] {
			if colCell == tagVal.Alias {
				tagVal.Alias = field.Name()
				cellMap[i] = *tagVal
			}
		}
	}
	paramMap := make([]map[string]interface{}, 0)
	for _, row := range rows[1:] {
		param := make(map[string]interface{})
		for i, colCell := range row {
			fieldName := cellMap[i]
			if fieldName.Pattern == nil {
				param[fieldName.Alias] = colCell
			} else {
				for k, v := range fieldName.Pattern {
					if v == colCell {
						param[fieldName.Alias] = k
					}
				}
			}
		}
		paramMap = append(paramMap, param)
	}

	err = gconv.Structs(paramMap, &params)
	return
}

// getColumnName 生成列名
// Excel的列名规则是从A-Z往后排;超过Z以后用两个字母表示，比如AA,AB,AC;两个字母不够以后用三个字母表示，比如AAA,AAB,AAC
// 这里做数字到列名的映射：0 -> A, 1 -> B, 2 -> C
// maxColumnRowNameLen 表示名称框的最大长度，假设数据是10行，1000列，则最后一个名称框是J1000(如果有表头，则是J1001),是4位
// 这里根据 maxColumnRowNameLen 生成切片，后面生成名称框的时候可以复用这个切片，而无需扩容
func getColumnName(column, maxColumnRowNameLen int) []byte {
	const A = 'A'
	if column < MaxCharCount {
		// 第一次就分配好切片的容量
		slice := make([]byte, 0, maxColumnRowNameLen)
		return append(slice, byte(A+column))
	} else {
		// 递归生成类似AA,AB,AAA,AAB这种形式的列名
		return append(getColumnName(column/MaxCharCount-1, maxColumnRowNameLen), byte(A+column%MaxCharCount))
	}
}

// getColumnRowName 生成名称框
// Excel的名称框是用A1,A2,B1,B2来表示的，这里需要传入前一步生成的列名切片，然后直接加上行索引来生成名称框，就无需每次分配内存
func getColumnRowName(columnName []byte, rowIndex int) (columnRowName string) {
	l := len(columnName)
	columnName = strconv.AppendInt(columnName, int64(rowIndex), 10)
	columnRowName = string(columnName)
	// 将列名恢复回去
	columnName = columnName[:l]
	return
}
